#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/time.h>

#include <utils.h>

#include <string>

static void
_read_link(const char *link, std::string& real)
{
    char buf[4096];
    int  r;

    if ((r = readlink(link, buf, 4096)) > 0) {
        buf[r] = 0;
        real.assign(buf);
    }
}

// return -1 when sys-call failed
int dir_lookup(const char        *path,
               dir_lookup_func_t  func)
{
    DIR           *dir;
    struct dirent *ent;

    dir = opendir(path);
    if (dir == NULL) {
        message(MSG_LEVEL_ERROR, "open directory `%s' failed: %s\n",
                path, strerror(errno));
        return -1;
    }

    for (;;) {
        ent = readdir(dir);
        if (ent == NULL)
            break;

        if (ent->d_name[0] == '.')
            continue;

        std::string filename = path;
        filename.push_back('/');
        filename += ent->d_name;

        int type = 0;

        /* 增加了符号连接的支持。不过只支持一层符号连接，多层的现在不
         * 支持。比如说连接到连接是不行的。 */

        // if (DT_LNK == ent->d_type) {
        //     std::string real;
        //     _read_link(ent->d_name, real);

        //     std::string abs_path;

        //     get_abs_path(real.c_str(), abs_path);

        //     std::string adj;

        //     adjust_path(abs_path.c_str(), adj);

        //     struct stat sb;

        //     if (stat(adj.c_str(), &sb) < 0)
        //         continue;

        //     if (S_ISREG(sb.st_mode)) {
        //         type = 1;
        //     }
        //     else if (S_ISDIR(sb.st_mode)) {
        //         type = 2;
        //     }

        //     filename = adj;
        // }

        // regular file
        if (DT_REG == ent->d_type || type == 1) {
            if (!func(filename.c_str()))
                break;
        }
        else if (DT_DIR == ent->d_type || type == 2) {
            // check ignore ?
            if (g_ignored_dir.size() > 0) {
                int i;

                for (i = 0; i < (int) g_ignored_dir.size(); i++) {
                    if (g_ignored_dir[i] == ent->d_name) {
                        message(MSG_LEVEL_MESSAGE, "ignored `%s'\n", filename.c_str());
                        break;
                    }
                }

                if (i < (int) g_ignored_dir.size())
                    continue;
            }
            if (dir_lookup(filename.c_str(), func) < 0)
                return -1;
        }
    }

    closedir(dir);

    return 0;
}

int get_cwd(std::string& path)
{
    char buf[4096];

    if (getcwd(buf, 4096) == NULL) {
        message(MSG_LEVEL_ERROR, "getcwd failed, path too long\n");
        return -1;
    }

    path = buf;
    return 0;
}

int get_abs_path(const char   *path,
                 std::string&  result)
{
    if (*path != '/') {
        std::string cwd;
        if (get_cwd(cwd) < 0)
            return -1;
        result = cwd;
        result.push_back('/');
        result += path;
    }
    else
        result = path;

    return 0;
}

// read the file entire
int read_entire_file(const char  *fn,
                     char       **result)
{
    int fd = open(fn, O_RDONLY);
    if (fd < 0) {
        message(MSG_LEVEL_ERROR, "open file `%s' failed: %s\n",
                fn, strerror(errno));
        return -1;
    }
    struct stat st;
    if (fstat(fd, &st) < 0) {
        message(MSG_LEVEL_ERROR, "stat file `%s' failed: %s\n",
                fn, strerror(errno));
        close(fd);
        return -1;
    }

    int size = st.st_size;

    if (size == 0) {
        close(fd);
        return 0;
    }

    char *buf = (char *) malloc (size + 1);

    if (buf == NULL) {
        message(MSG_LEVEL_ERROR, "alloc memory %d bytes failed: %s\n",
                size, strerror(errno));
        close(fd);
        return -1;
    }

    int i = 0;
    while (i < size) {
        int r = read(fd, buf + i, size - i);
        if (r > 0)
            i += r;
        else if (r == 0)
            break;
        else if (r < 0 && errno != EINTR)
            break;
    }

    close(fd);

    if (i != size) {
        message(MSG_LEVEL_ERROR, "read `%s' failed: %s\n", strerror(errno));
        free(buf);
        return -1;
    }

    buf[size] = 0;

    *result = buf;
    return size;
}

struct ProcessData {
    pid_t               pid;

    pid_t               pid_bak;

    int                 infd;

    int                 outfd;

    int                 errfd;

    ProcessData() : pid(-1)
                  , pid_bak(-1)
                  , infd(-1)
                  , outfd(-1)
                  , errfd(-1)
    {
    }
};

Process::Process() : _sys(NULL), _user_data(NULL)
{
    ProcessData *pd = new ProcessData();

    if (pd == NULL) {
        message(MSG_LEVEL_ERROR, "alloc memory failed: %s\n",
                strerror(errno));
        return;
    }

    _sys = (void *) pd;
}

Process::~Process()
{
    ProcessData *pd = (ProcessData *) _sys;

    if (pd) {
        clean_resources();
        delete pd;
    }
}

bool Process::run(std::string    name,
                  StringVector   args,
                  int            flags)
{
    ProcessData *pd = (ProcessData *) _sys;

    if (pd == NULL) {
        pd = new ProcessData();
        if (pd == NULL)
            return false;
        _sys = (void *) pd;
    }

    clean_resources();

    // create the pipes
    int stdin_pipe[2]  = { -1, -1 };
    int stdout_pipe[2] = { -1, -1 };
    int stderr_pipe[2] = { -1, -1 };

    pid_t   pid;
    char  **argv;
    int     i;

    if (flags & PROC_BIND_STDIN) {
        if (pipe(stdin_pipe) < 0) {
            message(MSG_LEVEL_ERROR, "create pipe failed: %s\n",
                    strerror(errno));
            return false;
        }
    }
    if (flags & PROC_BIND_STDOUT) {
        if (pipe(stdout_pipe) < 0) {
            message(MSG_LEVEL_ERROR, "create pipe failed: %s\n",
                    strerror(errno));
            goto pipe_in;
        }
    }
    if (flags & PROC_BIND_STDERR) {
        if (pipe(stderr_pipe) < 0) {
            message(MSG_LEVEL_ERROR, "create pipe failed: %s\n",
                    strerror(errno));
            goto pipe_in_out;
        }
    }

    pid = fork();

    if (pid < 0) {
        message(MSG_LEVEL_ERROR, "fork failed: %s\n",
                strerror(errno));
        goto pipe_all;
    }

    // parent process
    if (pid > 0) {
        close(stdin_pipe[0]);
        close(stdout_pipe[1]);
        close(stderr_pipe[1]);

        pd->infd  = stdin_pipe[1];
        pd->outfd = stdout_pipe[0];
        pd->errfd = stderr_pipe[0];

        pd->pid = pid;
        pd->pid_bak = pid;

        if (pd->outfd >= 0)
            fcntl(pd->outfd, F_SETFL, O_NONBLOCK);
        if (pd->errfd >= 0)
            fcntl(pd->errfd, F_SETFL, O_NONBLOCK);

        _run_ticks = get_ticks();
    }
    else {
        // make the argument vector
        argv = (char **)
            malloc (sizeof (char *) * (args.size() + 2));
        for (i = 0; i < (int) args.size(); i++) {
            argv[i + 1] = strdup(args[i].c_str());
        }
        argv[i + 1] = NULL;
        argv[0] = strdup(name.c_str());

        // re-bind
        close(stdin_pipe[1]);
        close(stdout_pipe[0]);
        close(stderr_pipe[0]);

        if (flags & PROC_BIND_STDIN) {
            dup2(stdin_pipe[0], 0);
            close(stdin_pipe[0]);
        }
        if (flags & PROC_BIND_STDOUT) {
            dup2(stdout_pipe[1], 1);
            close(stdout_pipe[1]);
        }
        if (flags & PROC_BIND_STDERR) {
            dup2(stderr_pipe[1], 2);
            close(stderr_pipe[1]);
        }

        execv(name.c_str(), argv);

        fprintf(stderr, "exec failed: %s\n", strerror(errno));

        exit(1);
    }

    return true;

pipe_all:
    close(stderr_pipe[0]);
    close(stderr_pipe[1]);

pipe_in_out:
    close(stdout_pipe[0]);
    close(stdout_pipe[1]);

pipe_in:
    close(stdin_pipe[0]);
    close(stdin_pipe[1]);

    return false;
}

bool Process::running()
{
    ProcessData *pd = (ProcessData *) _sys;

    return (pd && pd->pid >= 0);
}

static int _fmt_buffer_size = 0;

static char *_fmt_buffer = NULL;

int Process::write_stdin(const char *fmt, ...)
{
    int      n;
    va_list  ap;
    char    *np;

    ProcessData *pd = (ProcessData *) _sys;

    if (pd == NULL || pd->infd < 0)
        return 0;

    if (_fmt_buffer == NULL) {
        _fmt_buffer_size = 4096;
        _fmt_buffer = (char *) malloc (_fmt_buffer_size);

        if (_fmt_buffer == NULL) {
            message(MSG_LEVEL_ERROR, "alloc mem failed: %s\n",
                    strerror(errno));
            return -1;
        }
    }

    for (;;) {
        va_start(ap, fmt);
        n = vsnprintf(_fmt_buffer, _fmt_buffer_size, fmt, ap);
        va_end(ap);

        if (n > -1 && n < _fmt_buffer_size)
            break;

        _fmt_buffer_size *= 2;
        if ((np = (char *) realloc (_fmt_buffer, _fmt_buffer_size)) == NULL) {
            free(_fmt_buffer);
            _fmt_buffer = NULL;
            message(MSG_LEVEL_ERROR, "alloc mem failed: %s\n",
                    strerror(errno));
            return -1;
        }
        else {
            _fmt_buffer = np;
        }
    }

    int curr = 0;
    int fd   = pd->infd;

    while (curr < n) {
        int r = write(fd, _fmt_buffer + curr, n - curr);

        if (r > 0) {
            curr += r;
        }
        else if (r <= 0) {
            if (errno != EAGAIN && errno != EINTR) {
                message(MSG_LEVEL_ERROR, "write fd %d failed: %s\n",
                        fd, strerror(errno));
                break;
            }
        }
    }

    return curr;
}

static int _read_fd(int fd, char *buf, int size)
{
    int curr;

    curr = 0;
    while (curr < size) {
        int r = read(fd, buf + curr, size - curr);

        if (r > 0)
            curr += r;
        else if (r == 0)
            break;
        else if (r < 0) {
            if (errno != EINTR) {
                break;
            }
        }
    }

    return curr;
}

int Process::read_stdout(char *buf, int size)
{
    ProcessData *pd = (ProcessData *) _sys;

    if (pd == NULL || pd->outfd < 0)
        return 0;

    return _read_fd(pd->outfd, buf, size);
}

int Process::read_stderr(char *buf, int size)
{
    ProcessData *pd = (ProcessData *) _sys;

    if (pd == NULL || pd->errfd < 0)
        return 0;

    return _read_fd(pd->errfd, buf, size);
}

bool Process::wait_proc(int  flag,
                        int *exited,
                        int *exit_type,
                        int *exit_value)
{
    ProcessData *pd = (ProcessData *) _sys;

    if (pd == NULL)
        return false;

    pid_t pid = pd->pid;

    if (pid < 0) {
        *exited = 1;
        return true;
    }

    // pid_t waitpid(pid_t pid, int *status, int options);
    int status = 0;
    pid_t r = waitpid(pid, &status,
                      ((flag == PROC_WAIT)
                       ? 0
                       : (WNOHANG | WUNTRACED)));

    if (r < 0) {
        message(MSG_LEVEL_ERROR, "wait proc %d failed, return %d: %s\n",
                pid, r, strerror(errno));
        pd->pid = -1;
        return false;
    }
    else if (r != pid) {
        *exited = 0;
        return true;
    }

    if (WIFEXITED(status)) {
        *exited = 1;
        *exit_type = PROC_EXIT_NORMAL;
        *exit_value = WEXITSTATUS(status);

        pd->pid = -1;
    }
    else if (WIFSIGNALED(status)) {
        *exited = 1;
        *exit_type = PROC_EXIT_SIGNAL;
        *exit_value = WTERMSIG(status);

        pd->pid = -1;
    }
    else if (WCOREDUMP(status)) {
        *exited = 1;
        *exit_type = PROC_EXIT_CORE_DUMP;

        pd->pid = -1;
    }
    else {
        message(MSG_LEVEL_MESSAGE, "got status %d\n", status);
    }

    return true;
}

void Process::clean_resources()
{
    int exited;
    int exit_type;
    int exit_value;

    ProcessData *pd = (ProcessData *) _sys;

    if (pd == NULL)
        return;

    if (pd->pid >= 0) {
        if (wait_proc(PROC_NO_WAIT, &exited, &exit_type, &exit_value)) {
            if (!exited) {
                // send the kill signal to the child process
                kill(pd->pid, SIGKILL);

                wait_proc(PROC_WAIT, &exited, &exit_type, &exit_value);
            }

            pd->pid = -1;
        }
    }

    if (pd->infd >= 0) {
        close(pd->infd);
        pd->infd = -1;
    }

    if (pd->outfd >= 0) {
        close(pd->outfd);
        pd->outfd = -1;
    }

    if (pd->errfd >= 0) {
        close(pd->errfd);
        pd->errfd = -1;
    }
}

void sleep_ms(int ms)
{
    struct timeval t;
    t.tv_sec = ms / 1000;
    t.tv_usec = (ms % 1000) * 1000;
    select(0, NULL, NULL, NULL, &t);
}

static struct timeval _start_time;

static bool _start_time_setup = false;

Uint64 get_ticks()
{
    if (!_start_time_setup) {
        gettimeofday(&_start_time, NULL);
        _start_time_setup = true;
    }

    struct timeval now;
    gettimeofday(&now, NULL);

    Uint64 r = now.tv_sec * 1000000 + now.tv_usec;
    r -= _start_time.tv_sec * 1000000 + _start_time.tv_usec;

    return r / 1000;
}

int test(const char *fn, char c)
{
    struct stat s;

    if (stat(fn, &s) < 0)
        return 0;

    switch (c) {
    case 'f': return S_ISREG(s.st_mode);
    case 'd': return S_ISDIR(s.st_mode);
    case 'L': return S_ISLNK(s.st_mode);
    }
    return 0;
}

int create_dir(const char *path)
{
    std::string top;

    while (*path == FS_SEP)
        top.push_back(*path++);

    for (;;) {
        while (*path && *path != FS_SEP)
            top.push_back(*path++);

        if (mkdir(top.c_str(), 0777) < 0
            && errno != EEXIST)
            return -1;

        if (*path == 0)
            break;

        top.push_back(FS_SEP);
        ++path;

        while (*path == FS_SEP)
            ++path;

        if (*path == 0)
            break;
    }

    return 0;
}
